package XMLScene;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Vector;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import Tracer.RTColor;
import Tracer.RTVector;

/**
 * XML parser class, core can get objects/light/camera after processing finish.
 * 
 * @author Hongyi Zhao
 * 
 */
public class RTImportXML extends DefaultHandler {
	private RTSurface iCurrentSurface;
	private RTVector iCurrentVector;
	private RTColor iCurrentColor;

	private RTColor iBackground = new RTColor(0.0f, 0.0f, 0.0f);
	// Camera
	private RTCamera iCamera;
	private RTVector iCurrentLocation;
	private RTVector iCurrentLookat;
	private RTVector iCurrentSky;
	// Surface
	private float iCurrentDiffuse = Float.MAX_VALUE;
	private float iCurrentReflect = Float.MAX_VALUE;
	// Sphere
	private float iCurrentRadius = Float.MAX_VALUE;
	private RTVector iCurrentPole;
	private RTVector iCurrentEquator;
	// Pigment
	private boolean iPigmentIsPureColor;
	private String iCurrentPPMPath;
	// Plane
	private float iCurrentDistance = Float.MAX_VALUE;
	private RTVector iCurrentNormal;
	private RTVector iCurrentPoint;
	// Triangle
	private RTVector iCurrentC0;
	private RTVector iCurrentV1;
	private RTVector iCurrentV2;

	private ArrayList<String> iElementStack;

	private Vector<RTIObject> iObjectVector;
	private Vector<RTLight> iLightVector;

	/**
	 * Constructor of the class, initialize members.
	 */
	public RTImportXML() {
		iElementStack = new ArrayList<String>();
	}

	/**
	 * Push the element when a XML element starts processing.
	 * 
	 * @param elementName
	 *            The name of the element.
	 */
	private void pushElement(final String elementName) {
		iElementStack.add(elementName);
	}

	/**
	 * Pops the element from stack when a XML element ends processing.
	 * 
	 * @return The name of the element to be popped from stack.
	 */
	private String popElement() {
		String elementName = (String) iElementStack
				.get(iElementStack.size() - 1);
		iElementStack.remove(iElementStack.size() - 1);
		return elementName;
	}

	/**
	 * Before an element is pushed into stack, call this method to get the name
	 * of current element. Using this method to check the parent element's name.
	 *
	 * @return The top object on the stack.
	 */
	private String currentElement() {
		return (String) iElementStack.get(iElementStack.size() - 1);
	}

	/**
	 * Event handler, being called right before the start of processing XML.
	 */
	public final void startDocument() {
		System.out.println("Start parsing document ");
		iObjectVector = new Vector<RTIObject>();
		iLightVector = new Vector<RTLight>();
	}

	/**
	 * Event handler, being called right after the end of processing XML.
	 */
	public final void endDocument() {
		System.out.println("End parsing document ");
	}

	/**
	 * Private method called when "vector" element starts
	 * 
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startVectorElement(final Attributes attr) {
		Double x = new Double(attr.getValue("x"));
		Double y = new Double(attr.getValue("y"));
		Double z = new Double(attr.getValue("z"));
		iCurrentVector = new RTVector(x.doubleValue(), y.doubleValue(), z
				.doubleValue());
	}

	/**
	 * Private method called when "color" element starts
	 * 
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startColorElement(final Attributes attr) {
		Float red = new Float(attr.getValue("red"));
		Float green = new Float(attr.getValue("green"));
		Float blue = new Float(attr.getValue("blue"));
		iCurrentColor = new RTColor(red.floatValue(), green.floatValue(), blue
				.floatValue());

		if (currentElement() == "pigment") {
			iPigmentIsPureColor = true;
		}
	}

	/**
	 * Private method called when "finish" element starts
	 * 
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startFinishElement(final Attributes attr) {
		String diffuseString = attr.getValue("diffuse");
		if (diffuseString != null) {
			Float diffuse = new Float(attr.getValue("diffuse"));
			iCurrentDiffuse = diffuse.floatValue();
		}
		String reflectString = attr.getValue("reflect");
		if (reflectString != null) {
			Float reflect = new Float(reflectString);
			iCurrentReflect = reflect.floatValue();
		}
	}

	/**
	 * Private method called when "plane" element starts
	 *
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startPlaneElement(final Attributes attr) {
		String distanceString = attr.getValue("distance");
		if (distanceString != null) {
			Float distance = new Float(distanceString);
			iCurrentDistance = distance.floatValue();
		}
	}

	/**
	 * Private method called when "sphere" element starts
	 *
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startSphereElement(final Attributes attr) {
		Float radius = new Float(attr.getValue("radius"));
		iCurrentRadius = radius.floatValue();
	}

	/**
	 * Private method called when "ppm" element starts
	 *
	 * @param attr
	 *            Attributes of the element.
	 */
	private void startPPMElement(final Attributes attr) {
		iCurrentPPMPath = new String(attr.getValue("file"));
		iPigmentIsPureColor = false;
	}

	/**
	 * Event handler, being called when a new element is going to be processed.
	 */
	public final void startElement(final String uri, final String lname,
			final String qname, final Attributes attributes) {
		if (lname == "color") {
			startColorElement(attributes);
		} else if (lname == "vector") {
			startVectorElement(attributes);
		} else if (lname == "finish") {
			startFinishElement(attributes);
		} else if (lname == "sphere") {
			startSphereElement(attributes);
		} else if (lname == "ppm") {
			startPPMElement(attributes);
		} else if (lname == "plane") {
			startPlaneElement(attributes);
		}
		pushElement(lname);
	}

	/**
	 * Event handler, being called when a element ends processing.
	 */
	public final void endElement(final String ns, final String lname,
			final String qname) {
		if (lname == "location") {
			iCurrentLocation = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "look_at") {
			iCurrentLookat = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "sky") {
			iCurrentSky = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "camera") {
			iCamera = new RTCamera(iCurrentLocation, iCurrentSky,
					iCurrentLookat);
			iCurrentLocation = null;
			iCurrentLookat = null;
			iCurrentSky = null;
		} else if (lname == "background") {
			iBackground = new RTColor(iCurrentColor);
			iCurrentColor = null;
		} else if (lname == "surface") {
			if (iPigmentIsPureColor) {
				iCurrentSurface = new RTSurface(iCurrentColor);
			} else {
				try {
					iCurrentSurface = new RTSurface(iCurrentPPMPath);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
			}
			if (iCurrentDiffuse != Float.MAX_VALUE) {
				iCurrentSurface.setDiffuse(iCurrentDiffuse);
				iCurrentDiffuse = Float.MAX_VALUE;
			}
			if (iCurrentReflect != Float.MAX_VALUE) {
				iCurrentSurface.setReflect(iCurrentReflect);
				iCurrentReflect = Float.MAX_VALUE;
			}
			iCurrentColor = null;
			iCurrentPPMPath = null;
		} else if (lname == "pole") {
			iCurrentPole = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "equator") {
			iCurrentEquator = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "sphere") {
			RTSphere iCurrentSphere = null;
			iCurrentSphere = new RTSphere(iCurrentLocation, iCurrentRadius);
			if (iCurrentPole != null) {
				iCurrentSphere.setPole(iCurrentPole);
				iCurrentPole = null;
			}
			if (iCurrentEquator != null) {
				iCurrentSphere.setEquator(iCurrentEquator);
				iCurrentEquator = null;
			}
			if (iCurrentSurface != null) {
				iCurrentSphere.setSurface(iCurrentSurface);
				iCurrentSurface = null;
			}
			iObjectVector.add(iCurrentSphere);
			iCurrentSphere = null;
			iCurrentRadius = Float.MAX_VALUE;
			iCurrentLocation = null;
		} else if (lname == "light") {
			RTLight iCurrentLight = null;
			iCurrentLight = new RTLight(iCurrentVector);
			iLightVector.add(iCurrentLight);
			iCurrentLight = null;
			iCurrentVector = null;
		} else if (lname == "point") {
			iCurrentPoint = new RTVector(iCurrentVector);
		} else if (lname == "normal") {
			iCurrentNormal = new RTVector(iCurrentVector);
		} else if (lname == "plane") {
			RTPlane iCurrentPlane = null;
			if (iCurrentDistance != Float.MAX_VALUE) {
				iCurrentPlane = new RTPlane(RTVector
						.normaliseVectors(iCurrentNormal), iCurrentDistance);
				iCurrentDistance = Float.MAX_VALUE;
			} else if (iCurrentPoint != null) {
				iCurrentPlane = new RTPlane(RTVector
						.normaliseVectors(iCurrentNormal), iCurrentPoint);
				iCurrentPoint = null;
			}

			if (iCurrentSurface != null) {
				iCurrentPlane.setSurface(iCurrentSurface);
				iCurrentSurface = null;
			}

			iObjectVector.add(iCurrentPlane);
			iCurrentPlane = null;
			iCurrentNormal = null;
		} else if (lname == "c0") {
			iCurrentC0 = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "v1") {
			iCurrentV1 = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "c1") {
			iCurrentV1 = RTVector.sumVectors(iCurrentVector, RTVector
					.reverseVector(iCurrentC0));
			iCurrentVector = null;
		} else if (lname == "v2") {
			iCurrentV2 = new RTVector(iCurrentVector);
			iCurrentVector = null;
		} else if (lname == "c2") {
			iCurrentV2 = RTVector.sumVectors(iCurrentVector, RTVector
					.reverseVector(iCurrentC0));
			iCurrentVector = null;
		} else if (lname == "triangle") {
			RTTriangle iCurrentTriangle = null;
			iCurrentTriangle = new RTTriangle(iCurrentC0, iCurrentV1,
					iCurrentV2);
			if (iCurrentSurface != null) {
				iCurrentTriangle.setSurface(iCurrentSurface);
				iCurrentSurface = null;
			}
			iObjectVector.add(iCurrentTriangle);
			iCurrentTriangle = null;
			iCurrentC0 = null;
			iCurrentV1 = null;
			iCurrentV2 = null;
		}
		popElement();
	}

	/**
	 * Getter method of camera.
	 * 
	 * @return Return the RTCamera object.
	 */
	public final RTCamera getCamera() {
		return this.iCamera;
	}

	/**
	 * Getter of background.
	 * 
	 * @return Return the background in a RTColor object.
	 */
	public final RTColor getBackground() {
		return iBackground;
	}

	/**
	 * Getter of all objects in the XML including Sphere,Plane,Triangle.
	 * 
	 * @return Return a vector of all objects.
	 */
	public final Vector<RTIObject> getObjectList() {
		return this.iObjectVector;
	}

	/**
	 * Getter of all lights.
	 *
	 * @return Return a vector list of all lights object read from XML.
	 */
	public final Vector<RTLight> getLightList() {
		/**
		 * Remove the following three line code after assignment 2, These code
		 * make sure the light is at camera's location.
		 */
		return this.iLightVector;
	}
}